#include "Screens\GameScreen.h"
#include "Objects/GameObject.h"
#include "GameState.h"
#include "Objects/Objects.h"

#include <iostream>
#include <fstream>
#include <sstream>

GameScreen::GameScreen(GameState *gameState) : ScreenBase(gameState),
	m_world(b2Vec2(0.0f, -100.0f)),		 
	m_accumulator(0.0f),
	m_currentTime(0.0f),
	m_hasCheckpoint(false),
	m_dots(0),
	m_totalDots(0)
{
	
}


GameScreen::~GameScreen(){
	clean();
}


void GameScreen::init(){
	ScreenBase::init();

	m_world.SetContactListener(this);

	m_music.openFromFile(soundsPath+"WeAreCarbon.ogg");
	m_music.setLoop(true);

	loadState("level1.json");
	m_music.play();
}


void GameScreen::update(double dt){
	if ( dt > 0.25 ){
		dt = 0.25;	  // note: max frame time to avoid spiral of death
	}
    
	ScreenBase::update(dt);

	m_gameState->camera.update(dt);

	m_accumulator += dt;

	
	while ( m_accumulator >= TIMESTEP ){
		m_world.Step((float)TIMESTEP, 13, 8); 
		m_accumulator -= TIMESTEP;
	}

	if(!m_nextSaveState.empty()){
		saveState(m_nextSaveState);
	}

	if(!m_nextLoadState.empty()){
		loadState(m_nextLoadState);
	}
}


void GameScreen::draw(){
	ScreenBase::draw();
	drawUI();
}


void GameScreen::drawUI(){
	Texture *texture = m_gameState->getTextureManager().getTexture("uiLayout.png");

	glPushMatrix();
	{
		int size = 12;
		glTranslatef(-m_gameState->camera.widthUnits/2+size/1.8f, m_gameState->camera.heightUnits/2-0.275f*size/1.8f, 0); 
		glScalef(1.0f*size, 0.275f*size, 1.0f);

		sf::Texture::bind(texture); 

		glBegin(GL_QUADS);
		{
			glTexCoord2f(0.0f, 1.0f); glVertex3f( -0.5f, -0.5f, -0.2f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.5f, -0.5f, -0.2f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.5f, 0.5f, -0.2f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f( -0.5f, 0.5f, -0.2f);
		}
		glEnd();
	}
	glPopMatrix();
	
	sf::Texture::bind(NULL);

	m_gameState->getWindow()->pushGLStates();
	string dotsString;
	stringstream dsstream;
	dsstream << int(m_dots/m_totalDots*100.0f) << "%";
	dotsString = dsstream.str();
	Text text(dotsString, m_gameState->getFont(), 45);
	text.setPosition(80, 4);
	text.setColor(Color(80, 70, 25));
	m_gameState->getWindow()->draw(text);
	m_gameState->getWindow()->popGLStates();
}

void GameScreen::restoreCheckPoint(){
	if(m_hasCheckpoint){
		loadStateDeferred("checkpoint.json");
	}else{
		loadStateDeferred("level1.json");
	}
}


void GameScreen::saveCheckPoint(){
	saveStateDeferred("checkpoint.json");
	m_hasCheckpoint = true;
}


void GameScreen::loadState(const string &filename){
	clean();

	addDefaultObjects();

	ifstream in(levelsPath+filename);
	stringstream out;
	out << in.rdbuf();
	
	string outstring = out.str();
	
	char *cstr = new char[outstring.length()+1];
	std::strcpy(cstr, outstring.c_str());
	
	Document document;
	document.Parse<0>(cstr);

	const Value& objects = document["objects"];
	for(SizeType i = 0; i < objects.Size(); i++){
		GameObject *obj = createObject(objects[i]);
		if(obj){
			addObject(obj);
		}
	}

	const Value& spawn = document["spawn"];
	b2Vec2 spawnPos((float)spawn[SizeType(0)].GetDouble(),
					(float)spawn[SizeType(1)].GetDouble());

	m_dots = document["dots"].GetInt();

	m_totalDots = (float)document["totalDots"].GetInt();
	

	Player *player = new Player(m_gameState);
	player->setPosition(spawnPos);
	addObject(player);

	Fire *fire = new Fire(m_gameState);
	fire->setPosition(b2Vec2(0.0f, 2));
	addObject(fire);
	
	fire->setPlayer(player);
	m_player = player;

	delete[] cstr;

	m_nextLoadState = string();
	
}

void GameScreen::loadStateDeferred(const string &filename){
	m_nextLoadState = filename;
}

void GameScreen::saveState(const string &filename){
	string absFileName = levelsPath+filename;

	FILE *fp = fopen(absFileName.c_str(),"w");
	FileStream stream(fp);

	PrettyWriter<FileStream> writer(stream);
	writer.StartObject();
	writer.String("objects");
	writer.StartArray();
	
	list<GameObject*>::iterator it = m_objects.begin();
	int count = 0;
	while(it!=m_objects.end()){
		if(!(*it)->hasProperty("NOSAVE")){
			writer.StartObject();
			writeObject(writer, (*it));
			writer.EndObject(1);
		}
		++it;
		count++;
	}

	writer.EndArray(count);

	writer.String("spawn");
	writer.StartArray();
	writer.Double(m_player->getPosition().x);
	writer.Double(m_player->getPosition().y);
	writer.EndArray(2);

	writer.String("dots");
	writer.Int(m_dots);

	writer.String("totalDots");
	writer.Int((int)m_totalDots);

	writer.EndObject(3);

	

	fclose(fp);

	m_nextSaveState = string();
	
}

void GameScreen::saveStateDeferred(const string &filename){
	m_nextSaveState = filename;
}



void GameScreen::clean(){
	m_dots = 0;
	m_totalDots = 0;

	

	ScreenBase::clean();
}


void GameScreen::writeObject(PrettyWriter<FileStream> &writer, GameObject *object){
	b2Vec2 pos = object->getPosition();
	float rot = object->getRotation();
	b2Vec2 size = object->getSize();

	if(object->hasProperty("OneWayPlatform")){
		OneWayPlatform* cObject = static_cast<OneWayPlatform*>(object);
		writer.String("type");
		writer.String("OneWayPlatform");
		writer.String("width");
		writer.Int(cObject->getUnitsWide());
		writer.String("stepdown");
		writer.Int(cObject->hasProperty("stepdown"));
	}else if(object->hasProperty("Turtoze")){
		Turtoze* cObject = static_cast<Turtoze*>(object);
		writer.String("type");
		writer.String("Turtoze");
		writer.String("direction");
		writer.Int(cObject->getDirection());
	}else if(object->hasProperty("Isle")){
		Isle* cObject = static_cast<Isle*>(object);
		writer.String("type");
		writer.String("Isle");
	}else if(object->hasProperty("RotatingIsle")){
		RotatingIsle* cObject = static_cast<RotatingIsle*>(object);
		writer.String("type");
		writer.String("RotatingIsle");
		writer.String("center");
		writer.StartArray();
		writer.Double(cObject->getCenter().x);
		writer.Double(cObject->getCenter().y);
		writer.EndArray(3);
		writer.String("theta");
		writer.Double(cObject->getTheta());
		writer.String("original");
		writer.StartArray();
		writer.Double(cObject->getOriginalPosition().x);
		writer.Double(cObject->getOriginalPosition().y);
		writer.EndArray(3);
		pos = cObject->getOriginalPosition();
	}else if(object->hasProperty("MovingIsle")){
		MovingIsle* cObject = static_cast<MovingIsle*>(object);
		writer.String("type");
		writer.String("MovingIsle");
		writer.String("start");
		writer.StartArray();
		writer.Double(cObject->getStart().x);
		writer.Double(cObject->getStart().y);
		writer.EndArray(3);
		writer.String("end");
		writer.StartArray();
		writer.Double(cObject->getEnd().x);
		writer.Double(cObject->getEnd().y);
		writer.EndArray(3);
		writer.String("state");
		writer.Int(cObject->getState());
		writer.String("vel");
		writer.Double(cObject->getVel());
	}else if(object->hasProperty("Cog")){
		Cog* cObject = static_cast<Cog*>(object);
		writer.String("type");
		writer.String("Cog");
		writer.String("attached");
		writer.StartArray();
		for(int i = 0; i < cObject->getCreateObjectCount(); i++){
			writer.StartObject();
			writeObject(writer, cObject->getCreateObject(i));
			writer.EndObject(10000);
		}
		writer.EndArray(10000);
		
	}else if(object->hasProperty("GreenDot")){
		GreenDot* cObject = static_cast<GreenDot*>(object);
		writer.String("type");
		writer.String("GreenDot");
	}else if(object->hasProperty("StoneBlocks")){
		StoneBlocks* cObject = static_cast<StoneBlocks*>(object);
		writer.String("type");
		writer.String("StoneBlocks");
		writer.String("width");
		writer.Double(cObject->getUnitsWide());
		writer.String("height");
		writer.Double(cObject->getUnitsHigh());
	}else if(object->hasProperty("CheckPoint")){
		CheckPoint* cObject = static_cast<CheckPoint*>(object);
		writer.String("type");
		writer.String("CheckPoint");
	}else if(object->hasProperty("IceSpikes")){
		IceSpikes* cObject = static_cast<IceSpikes*>(object);
		writer.String("type");
		writer.String("IceSpikes");
	}else if(object->hasProperty("GameText")){
		GameText* cObject = static_cast<GameText*>(object);
		writer.String("type");
		writer.String("Text");
		writer.String("text");
		writer.String(cObject->getText().c_str());
	}else{
		writer.String("type");
		writer.String("IGNORE");
	}

	writer.String("pos");
	writer.StartArray();

	writer.Double(pos.x);
	writer.Double(pos.y);
	writer.EndArray(3);

	writer.String("rot");
	writer.Double(rot);

	writer.String("size");
	writer.StartArray();
	writer.Double(size.x);
	writer.Double(size.y);
	writer.EndArray(2);

}


GameObject* GameScreen::createObject(const Value &object){
	string type(object["type"].GetString());
	b2Vec2 pos((float)object["pos"][SizeType(0)].GetDouble(),
				(float)object["pos"][SizeType(1)].GetDouble());
	float rot = (float)object["rot"].GetDouble();
	b2Vec2 size((float)object["size"][SizeType(0)].GetDouble(),
				(float)object["size"][SizeType(1)].GetDouble());

	if(type == "OneWayPlatform"){
		int width = (int)object["width"].GetDouble();
		bool stepdown = (bool)object["stepdown"].GetInt();
		OneWayPlatform *platform = new OneWayPlatform(m_gameState, width, stepdown);
		platform->setPosition(pos);
		platform->setSize(size);
		platform->setRotation(rot);
		return platform;
	}else if(type == "Turtoze"){
		int direction = object["direction"].GetInt();
		Turtoze *turt = new Turtoze(m_gameState);
		turt->setPosition(pos);
		turt->setDirection(direction);
		return turt;
	}else if(type == "Isle"){
		Isle *isle = new Isle(m_gameState, size.x);
		isle->setPosition(pos);
		return isle;
	}else if(type == "RotatingIsle"){
		b2Vec2 center((float)object["center"][SizeType(0)].GetDouble(),
				(float)object["center"][SizeType(1)].GetDouble());
		float theta = (float)object["theta"].GetDouble();
		b2Vec2 original((float)object["original"][SizeType(0)].GetDouble(),
				(float)object["original"][SizeType(1)].GetDouble());
		RotatingIsle *isle = new RotatingIsle(m_gameState, center, size.x);
		isle->setPosition(pos);
		isle->setOriginalPosition(original);
		isle->setTheta(theta);
		
		return isle;
	}else if(type == "MovingIsle"){
		b2Vec2 start((float)object["start"][SizeType(0)].GetDouble(),
				(float)object["start"][SizeType(1)].GetDouble());
		b2Vec2 end((float)object["end"][SizeType(0)].GetDouble(),
				(float)object["end"][SizeType(1)].GetDouble());
		float vel = (float)object["vel"].GetDouble();
		int state = object["state"].GetInt();
		MovingIsle *isle = new MovingIsle(m_gameState, start, end, size.x);
		isle->setVel(vel);
		isle->setPosition(pos);
		isle->setState(state);
		return isle;
	}else if(type == "Cog"){
		const Value& attached = object["attached"];
		Cog *cog = new Cog(m_gameState);
		cog->setPosition(pos);
		for(SizeType j = 0; j < attached.Size(); j++){
			GameObject *obj = createObject(attached[j]);
			cog->addObject(obj);
		}
		return cog;
	}else if(type == "GreenDot"){
		GreenDot *dot = new GreenDot(m_gameState);
		dot->setPosition(pos);
		return dot;
	}else if(type == "StoneBlocks"){
		int width = (int)object["width"].GetDouble();
		int height = (int)object["height"].GetDouble();
		StoneBlocks *block = new StoneBlocks(m_gameState, width, height);
		block->setPosition(pos);
		return block;
	}else if(type == "CheckPoint"){
		CheckPoint *checkPoint = new CheckPoint(m_gameState);
		checkPoint->setPosition(pos);
		return checkPoint;
	}else if(type == "IceSpikes"){
		IceSpikes *iceSpikes = new IceSpikes(m_gameState, size);
		iceSpikes->setPosition(pos);
		iceSpikes->setRotation(rot);
		return iceSpikes;
	}else if(type == "Text"){
		GameText *text = new GameText(m_gameState);
		string textStr(object["text"].GetString());
		text->setPosition(pos);
		text->setRotation(rot);
		text->setText(textStr);
		return text;
	}


	return 0;
}


void GameScreen::addDefaultObjects(){
	PhysicalObject *wall = new PhysicalObject(m_gameState);
	wall->setVisible(false);
	wall->getBodyDef()->type = b2_staticBody;
	wall->getBodyDef()->fixedRotation = true;
	b2FixtureDef *fixDef = new b2FixtureDef;
	fixDef->filter.groupIndex = -1;
	b2PolygonShape *shape = new b2PolygonShape;
	shape->SetAsBox(2, 1000.0f);
	fixDef->shape = shape;
	fixDef->density = 2.0f;
	fixDef->restitution = 0.0f;
	fixDef->friction = 0;
	wall->addFixtureDef(fixDef);
	wall->setPosition(b2Vec2(-27, 0));
	addObject(wall);

	wall = new PhysicalObject(m_gameState);
	wall->setVisible(false);
	wall->getBodyDef()->type = b2_staticBody;
	wall->getBodyDef()->fixedRotation = true;
	fixDef = new b2FixtureDef;
	fixDef->filter.groupIndex = -1;
	shape = new b2PolygonShape;
	shape->SetAsBox(2, 1000.0f);
	fixDef->shape = shape;
	fixDef->density = 2.0f;
	fixDef->restitution = 0.0f;
	fixDef->friction = 0;
	wall->addFixtureDef(fixDef);
	wall->setPosition(b2Vec2(27.0f, 0));
	addObject(wall);

	Background *background = new Background(m_gameState);
	addObject(background, 9);

	BackgroundIsles *backgroundIsles = new BackgroundIsles(m_gameState);
	addObject(backgroundIsles, 8);

	FadeToBlack *fadeToBlack = new FadeToBlack(m_gameState, 0.5f, true);
	addObject(fadeToBlack);

}


void GameScreen::addObject(GameObject* object, int layer){
	if(object->hasProperty("physical")){
		PhysicalObject *temp = static_cast<PhysicalObject*>(object);
		b2Body *body = m_world.CreateBody(temp->getBodyDef());

		for(int i = 0; i < temp->getFixtureDefCount(); i++){
			b2FixtureDef *def = temp->getFixtureDef(i);
			body->CreateFixture(def);
		}

		body->SetUserData(temp);
		temp->setBody(body);
	}

	ScreenBase::addObject(object, layer);

}


void GameScreen::BeginContact(b2Contact* contact){
	PhysicalObject *objectA = (PhysicalObject*)contact->GetFixtureA()->GetBody()->GetUserData();
	objectA->beginContact(contact);

	PhysicalObject *objectB = (PhysicalObject*)contact->GetFixtureB()->GetBody()->GetUserData();
	objectB->beginContact(contact);
}


void GameScreen::EndContact(b2Contact* contact){
	PhysicalObject *objectA = (PhysicalObject*)contact->GetFixtureA()->GetBody()->GetUserData();
	objectA->endContact(contact);

	PhysicalObject *objectB = (PhysicalObject*)contact->GetFixtureB()->GetBody()->GetUserData();
	objectB->endContact(contact);
}


void GameScreen::PreSolve(b2Contact* contact, const b2Manifold* oldManifold){
	PhysicalObject *objectA = (PhysicalObject*)contact->GetFixtureA()->GetBody()->GetUserData();
	objectA->PreSolve(contact, oldManifold);

	PhysicalObject *objectB = (PhysicalObject*)contact->GetFixtureB()->GetBody()->GetUserData();
	objectB->PreSolve(contact, oldManifold);
}


void GameScreen::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse){
	PhysicalObject *objectA = (PhysicalObject*)contact->GetFixtureA()->GetBody()->GetUserData();
	objectA->PostSolve(contact, impulse);

	PhysicalObject *objectB = (PhysicalObject*)contact->GetFixtureB()->GetBody()->GetUserData();
	objectB->PostSolve(contact, impulse);
}

